home *** CD-ROM | disk | FTP | other *** search
/ MacHack 1994 / MacHack 1994.toast / MacHack™94 / Talks & Papers / Michael D. Crawford↵ / Word Services SDK 1.0.5 / Writeswell Jr. Source / ObText.c < prev    next >
Text File  |  1993-03-17  |  23KB  |  969 lines

  1. /* ObText.c
  2.  * ©1992 Working Software, Inc.
  3.  * This source code is copyrighted.  Permission is granted to use the Word Services
  4.  * portion of the Writeswell Jr. source code in your own programs, but you 
  5.  * may not distribute the Writeswell Jr. word-processor code as a 
  6.  * commercial product.  If you modify the code, please do not call it 
  7.  * Writeswell Jr. (or Writeswell.)  This will ensure that people understand the 
  8.  * program and don’t have to deal with a number of different versions with 
  9.  * who-knows-what going on in the code.
  10.  * 
  11.  * Writeswell Jr. and Writeswell are trademarks of Working Software, Inc.
  12.  * 26 Dec 91 Mike Crawford
  13.  */
  14.  
  15. #include <AppleEvents.h>
  16. #include <AEObjects.h>
  17. #include <AEPackObject.h>
  18. #include <AERegistry.h>
  19. #include "AppEvents.h"
  20. #include "WordServices.h"
  21. #include "ObWind.h"
  22. #include "ObText.h"
  23. #include "Gripe.h"
  24. #include "Scroll.h"
  25. #include "TBConstants.h"
  26. #include "TBGlobals.h"
  27.  
  28. /* Given the the direct object of an event is a TextEdit text token (or property thereof),
  29.  * do the requested event.
  30.  */
  31.  
  32. OSErr DispatchTEText( AEDesc *tokenPtr,
  33.                         AppleEvent *theAppleEventPtr,
  34.                         AppleEvent *replyEventPtr,
  35.                         long refCon )
  36. {
  37.     OSErr            err;
  38.     AEEventClass    theClass;
  39.     AEEventID        theID;
  40.     
  41.     /* This function is only for the Core suit.  Get the event ID from the appleEvent
  42.      */
  43.     
  44.     err = GetEventID( theAppleEventPtr, &theID );
  45.     
  46.     switch ( theID ){
  47.         case kAEGetData:
  48.             err = TETextGetDataHandler( tokenPtr, theAppleEventPtr, replyEventPtr, refCon );
  49.             break;
  50.         case kAESetData:
  51.             err = TETextSetDataHandler( tokenPtr, theAppleEventPtr, replyEventPtr, refCon );
  52.             break;
  53.         default:
  54.             err = errAEEventNotHandled;
  55.             break;
  56.     }
  57.     
  58.     return noErr;
  59. }
  60.  
  61. /*
  62.  * Event Handlers
  63.  */
  64.  
  65. /* Get Data */
  66.  
  67. OSErr TETextGetDataHandler( AEDesc *tokenPtr,
  68.                         AppleEvent *theAppleEventPtr,
  69.                         AppleEvent *replyEventPtr,
  70.                         long refCon )
  71. {
  72.     TEHandle        textH;
  73.     DescType        propCode;
  74.     short            startPos;
  75.     short            length;
  76.     TETextTokenBody    **tokHdl;
  77.     Handle            rawTextHdl;
  78.     AEDesc            replyValue;
  79.     long            rawSize;
  80.     OSErr            err;
  81.     SignedByte        hState;
  82.  
  83.     /* Sanity check */
  84.     if ( tokenPtr->descriptorType != typeTEText ){
  85.         Gripe( "\pGot wrong token type" );
  86.         return errAEEventNotHandled;
  87.     }
  88.  
  89.     tokHdl = (TETextTokenBody**)(tokenPtr->dataHandle);
  90.     
  91.     textH = (*tokHdl)->textH;
  92.     propCode = (*tokHdl)->propertyCode;
  93.     startPos = (*tokHdl)->startPos;
  94.     length = (*tokHdl)->length;
  95.     
  96.     if ( !textH ){
  97.         Gripe( "\pAttempting to get data for non-existent TextEdit Record" );
  98.         return errAENoSuchObject;
  99.     }
  100.  
  101.     switch ( propCode ){
  102.         case typeNull:
  103.             /* This is a magic number for "Not A Property".  I don't know if this
  104.              * is really kosher - gotta ask, but it is a convenience.
  105.              */
  106.             
  107.             /* return all of the text in the specified range */
  108.             rawTextHdl = (Handle)TEGetText( textH );
  109.             
  110.             /* Check the range to be sure the text is really there */
  111.             rawSize = GetHandleSize( rawTextHdl );
  112.             
  113.             if ( rawSize < startPos + length ){
  114.                 return errAECorruptData;
  115.             }
  116.             
  117.             hState = HGetState( rawTextHdl );
  118.             HLock( rawTextHdl );
  119.             
  120.             err = AECreateDesc( typeChar,
  121.                                 (char*)*rawTextHdl + startPos,
  122.                                 (Size)length,
  123.                                 &replyValue );
  124.  
  125.             HSetState( rawTextHdl, hState );
  126.  
  127.             if ( err ){
  128.                 Gripe( "\pAECreateDesc failed to create text replyValue" );
  129.                 return err;
  130.             }
  131.  
  132.             break;
  133.         case pClass:
  134.         case pColor:
  135.         case pFont:
  136.         case pPointSize:
  137.         case pScriptTag:
  138.         case pTextStyles:
  139.             Gripe( "\pGot a property type we do not yet implement" );
  140.             return errAENoSuchObject;
  141.             break;
  142.         default:
  143.             Gripe( "\pUnknown property type" );
  144.             return errAENoSuchObject;
  145.             break;
  146.     }
  147.     
  148.     /* At this point we have some kind of descriptor to stick in the reply */
  149.     
  150.     err = AEPutParamDesc( replyEventPtr,
  151.                             keyDirectObject,
  152.                             &replyValue );
  153.     if ( err ){
  154.         Gripe( "\pAEPutParamDesc failed" );
  155.         return err;
  156.     }
  157.     
  158.     err = AEDisposeDesc( &replyValue );
  159.     if ( err ){
  160.         Gripe( "\pAEDisposeDesc failed" );
  161.         return err;
  162.     }
  163.  
  164.     return noErr;
  165. }/* TETextGetDataHandler */
  166.  
  167. /* Set Data */
  168.  
  169. OSErr TETextSetDataHandler( AEDesc *tokenPtr,
  170.                         AppleEvent *theAppleEventPtr,
  171.                         AppleEvent *replyEventPtr,
  172.                         long refCon )
  173. {
  174.     TEHandle        textH;
  175.     DescType        propCode;
  176.     short            startPos;
  177.     short            length;
  178.     TETextTokenBody    **tokHdl;
  179.     AEDesc            newValue;
  180.     AEDesc            textValue;
  181.     AEDesc            boolValue;
  182.     SignedByte        hState;
  183.     long            newTextLen;
  184.     AEDesc            propValToken;
  185.     OSErr            err;
  186.  
  187.     /* Sanity check */
  188.     if ( tokenPtr->descriptorType != typeTEText ){
  189.         Gripe( "\pGot wrong token type" );
  190.         return errAEEventNotHandled;
  191.     }
  192.  
  193.     tokHdl = (TETextTokenBody**)(tokenPtr->dataHandle);
  194.     
  195.     textH = (*tokHdl)->textH;
  196.     propCode = (*tokHdl)->propertyCode;
  197.     startPos = (*tokHdl)->startPos;
  198.     length = (*tokHdl)->length;
  199.     
  200.     if ( !textH ){
  201.         Gripe( "\pAttempting to set data for non-existent TextEdit Record" );
  202.         return errAENoSuchObject;
  203.     }
  204.     
  205.     /* Get the value to set, whatever it is */
  206.     
  207.     err = AEGetParamDesc( theAppleEventPtr,
  208.                             keyAEData,
  209.                             typeWildCard,
  210.                             &newValue );
  211.     if ( err ){
  212.         Gripe( "\pAEGetParamDesc failed to get keyAEData" );
  213.         return err;
  214.     }                        
  215.  
  216.     switch ( propCode ){
  217.         case typeNull:
  218.             /* This is a magic number for "Not A Property".  I don't know if this
  219.              * is really kosher - gotta ask, but it is a convenience.
  220.              */
  221.             
  222.             /* Set the data in the specified range */
  223.             
  224.             /* STUB Look for the optional parameter to see where to actually
  225.              * set the text at
  226.              */
  227.             
  228.             err = AECoerceDesc( &newValue, typeChar, &textValue );
  229.             if ( err ){
  230.                 Gripe( "\pAECoerceDesc failed to coerce to text" );
  231.                 return err;
  232.             }
  233.             
  234.             TESetSelect( (long)startPos, (long)startPos + length, textH );
  235.             TEDelete( textH );
  236.             
  237.             newTextLen = GetHandleSize( textValue.dataHandle );
  238.             
  239.             hState = HGetState( textValue.dataHandle );
  240.             HLock( textValue.dataHandle );
  241.             
  242.             TEInsert( (Ptr)(*textValue.dataHandle), newTextLen, textH );
  243.             
  244.             gDocDirty = true;
  245.             
  246.             HSetState( textValue.dataHandle, hState );
  247.             
  248.             err = AEDisposeDesc( &textValue );
  249.             if ( err ){
  250.                 Gripe( "\pAEDisposeDesc failed" );
  251.                 return err;
  252.             }
  253.             
  254.             break;
  255.  
  256.         case pBackgroundHilite:
  257.         
  258.             /* Make sure that we have a boolean value */
  259.  
  260.             err = AECoerceDesc( &newValue, typeBoolean, &boolValue );
  261.             if ( err ){
  262.                 Gripe( "\pAECoerceDesc failed to coerce to typeBoolean" );
  263.                 return err;
  264.             }
  265.             
  266.             /* Set the selection in the TextEdit text */
  267.             
  268.             TESetSelect( startPos,
  269.                         startPos + length,
  270.                         textH );
  271.                                 
  272.             if ( *(Boolean*)(*boolValue.dataHandle) ){
  273.                 /* Display the selection, given that we are in the background */
  274.     
  275.                 ShowSelection( textH );
  276.                 
  277.                 /* STUB we should get the window from the token */
  278.                 SetVertScroll( gDocWindow, gVertScroll );
  279.         
  280.                 TEActivate( textH );    /* (Not normally done while in background) */
  281.             }else{
  282.                 /* STUB This isn't really the best thing to do.  What we really should
  283.                  * do is check to see whether we are the front process, and deactivate
  284.                  * if so.  For this demo, we can reasonably assume that we are not
  285.                  * the front process.
  286.                  */
  287.         
  288.                 TEDeactivate( textH );
  289.             }
  290.  
  291.             return noErr;
  292.             break;
  293.  
  294.         case pClass:
  295.         case pColor:
  296.         case pFont:
  297.         case pPointSize:
  298.         case pScriptTag:
  299.         case pTextStyles:
  300.             Gripe( "\pGot a property type we do not yet implement" );
  301.             return errAENoSuchObject;
  302.             break;
  303.         default:
  304.             Gripe( "\pUnknown property type" );
  305.             return errAENoSuchObject;
  306.             break;
  307.     }
  308.     
  309.     /* At this point we are done with the newValue descriptor */
  310.     
  311.     err = AEDisposeDesc( &newValue );
  312.     
  313.     if ( err ){
  314.         Gripe( "\pAEDisposeDesc newValue failed" );
  315.         return err;
  316.     }
  317.  
  318.     return noErr;
  319. }/* TETextSetDataHandler */
  320.  
  321. /*
  322.  * Object Accessors
  323.  */
  324.  
  325.  
  326. /* Return a text token given a Window token */
  327.  
  328. pascal OSErr TextFromWind(DescType desiredClass,
  329.                             const AEDesc *container,
  330.                             DescType containerClass,
  331.                             DescType form,
  332.                             const AEDesc *selectionData,
  333.                             AEDesc *theToken,
  334.                             long LongInt)
  335. {
  336.     AEDesc            longKeyData;
  337.     WindowPtr        wp;
  338.     long            count;
  339.     TETextTokenBody    tokData;
  340.     OSErr            err;
  341.  
  342.     /* Check that the container is what we intend.  This should only happen if we
  343.      * installed the token handler incorrectly.
  344.      */
  345.  
  346.     if ( container->descriptorType != cWindow )
  347.         return errAEEventNotHandled;
  348.     
  349.     /* find the window based on the key form */
  350.     
  351.     switch ( form ){
  352.         case formAbsolutePosition:
  353.             /* Make sure we really have a type long descriptor */
  354.             err = AECoerceDesc( selectionData, typeLongInteger, &longKeyData );
  355.             if ( err ){
  356.                 Gripe( "\pAECoerceDesc failed" );
  357.                 return err;
  358.             }
  359.             
  360.             count = **(long**)(longKeyData.dataHandle);
  361.             
  362.             /* We're done with the descriptor created in the coercion */
  363.             
  364.             err = AEDisposeDesc( &longKeyData );
  365.             if ( err ){
  366.                 Gripe( "\pAEDisposeDesc failed" );
  367.                 return err;
  368.             }
  369.  
  370. #ifndef HACK_OSPECS            
  371.             /* We only have one text field in the window */
  372.             
  373.             if ( count != 1 ){
  374.                 Gripe( "\pAttempting to get text from other than the first field" );
  375.                 return errAENoSuchObject;
  376.             }
  377. #else
  378.             /* Simulate the presence of two text blocks */
  379.             if ( count != 1 && count != 2){
  380.                 Gripe( "\pAttempting to get text from other than the first field" );
  381.                 return errAENoSuchObject;
  382.             }
  383. #endif
  384.  
  385.             wp = (*(WindTokenBody**)(container->dataHandle))->theWindowPtr;
  386.             
  387.             MakeTETextTokenBody( wp, &tokData, typeNull );
  388.             
  389.             err = AECreateDesc( typeTEText, (Ptr)&tokData, sizeof( tokData ), theToken );
  390.             if ( err ){
  391.                 Gripe( "\pAECreateDesc failed to create a token" );
  392.                 return err;
  393.             }
  394.             
  395.             return noErr;
  396.             break;
  397.         case formRelativePosition:
  398.         case formTest:
  399.         case formRange:
  400.         case formPropertyID:
  401.             Gripe( "\pGot a formPropertyID" );
  402.             break;
  403.         case formName:
  404.             return errAEEventNotHandled;    /* Flesh this out later */
  405.             break;
  406.         default:
  407.             Gripe( "\pGot unexpected key form" );
  408.             return errAEEventNotHandled;
  409.     }
  410.         
  411.     return noErr;
  412. }
  413.  
  414. void MakeTETextTokenBody( WindowPtr wp, TETextTokenBody* tokDataPtr, DescType propCode )
  415. {
  416.     Handle            rawTextHdl;
  417.     short            textLen;
  418.     TEHandle        textH;
  419.  
  420.     textH = (TEHandle)GetWRefCon( wp );
  421.     
  422.     rawTextHdl = (Handle)TEGetText( textH );
  423.     textLen = (short)GetHandleSize( rawTextHdl );
  424.  
  425.     /* Actually create the token that we return */
  426.     tokDataPtr->textH = textH;
  427.     tokDataPtr->startPos = 0;
  428.     tokDataPtr->length = textLen;                /* _All_ of the text */
  429.     tokDataPtr->propertyCode = typeNull;        /* This means it's not a property */
  430.  
  431.     return;
  432. }
  433.  
  434. /* return a word token from a TextEdit text token.  The word is actually still a TEText */
  435.  
  436. pascal OSErr WordFromTEText(DescType desiredClass,
  437.                             const AEDesc *container,
  438.                             DescType containerClass,
  439.                             DescType form,
  440.                             const AEDesc *selectionData,
  441.                             AEDesc *theToken,
  442.                             long LongInt)
  443. {
  444.     AEDesc            longKeyData;
  445.     WindowPtr        wp;
  446.     long            count;
  447.     TETextTokenBody    tokData;
  448.     Handle            rawTextHdl;
  449.     char            *textPtr;
  450.     short            i;
  451.     short            textLen;
  452.     TEHandle        textH;
  453.     short            oldPos;
  454.     short            newPos;
  455.     short            offSet;
  456.     short            oldLength;
  457.     OSErr            err;
  458.  
  459.     /* Check that the container is what we intend.  This should only happen if we
  460.      * installed the token handler incorrectly.
  461.      */
  462.  
  463.     if ( container->descriptorType != typeTEText )
  464.         return errAEEventNotHandled;
  465.     
  466.     /* find the text based on the key form */
  467.     
  468.     switch ( form ){
  469.         case formAbsolutePosition:
  470.             /* Make sure we really have a type long descriptor */
  471.             err = AECoerceDesc( selectionData, typeLongInteger, &longKeyData );
  472.             if ( err ){
  473.                 Gripe( "\pAECoerceDesc failed" );
  474.                 return err;
  475.             }
  476.             
  477.             count = **(long**)(longKeyData.dataHandle);
  478.             
  479.             /* We're done with the descriptor created in the coercion */
  480.             
  481.             err = AEDisposeDesc( &longKeyData );
  482.             if ( err ){
  483.                 Gripe( "\pAEDisposeDesc failed" );
  484.                 return err;
  485.             }
  486.             
  487.             /* This is a really, really rude way to find a word.  Assume that
  488.              * each word is separated by just one space (we will really want to
  489.              * allow for runs of spaces or other word-separating characters.  The
  490.              * following method is basically just plain wrong, but will suffice for
  491.              * illustration.
  492.              *
  493.              * Also note that the first word in a block might come after a run of
  494.              * spaces.
  495.              */
  496.             
  497.             textH = (*(TETextTokenBody**)(container->dataHandle))->textH;
  498.  
  499.             rawTextHdl = (Handle)TEGetText( textH );
  500.             
  501.             oldPos = (*(TETextTokenBody**)(container->dataHandle))->startPos;
  502.  
  503.             textPtr = *rawTextHdl + oldPos;    /* Deref'ed a handle! */
  504.             
  505.             oldLength = (*(TETextTokenBody**)(container->dataHandle))->length;
  506.             
  507.             for ( i = oldLength ; i; i-- ){
  508.                 if ( count == 0 )                /* Hmm... thought it was 1-based */
  509.                     break;
  510.                 if ( *textPtr++ == ' ' )
  511.                     count--;
  512.             }
  513.  
  514.             if ( i == 0 ){
  515.                 Gripe( "\pRan off end of text before finding word" );
  516.                 return errAENoSuchObject;
  517.             }
  518.             
  519.             offSet = oldLength - i;
  520.             
  521.             newPos = oldPos + offSet;
  522.             
  523.             /* now find the end of the word */
  524.  
  525.             textPtr = *rawTextHdl + newPos;    /* Deref'ed a handle! */
  526.  
  527.             for ( i = oldLength - offSet ; i; i-- ){
  528.                 if ( *++textPtr == ' ' )
  529.                     break;
  530.             }
  531.             
  532.             /* Actually create the token that we return */
  533.             tokData.textH = textH;
  534.             tokData.startPos = newPos;
  535.             tokData.length = oldLength - offSet - i + 1;
  536.             tokData.propertyCode = typeNull;        /* This means it's not a property */
  537.             
  538.             err = AECreateDesc( typeTEText, (Ptr)&tokData, sizeof( tokData ), theToken );
  539.             if ( err ){
  540.                 Gripe( "\pAECreateDesc failed to create a token" );
  541.                 return err;
  542.             }
  543.             
  544.             return noErr;
  545.             break;
  546.         case formRelativePosition:
  547.         case formTest:
  548.         case formRange:
  549.         case formPropertyID:
  550.         case formName:
  551.             return errAEEventNotHandled;    /* Flesh this out later */
  552.             break;
  553.         default:
  554.             Gripe( "\pGot unexpected key form" );
  555.             return errAEEventNotHandled;
  556.     }
  557.         
  558.     return noErr;
  559. }
  560.  
  561. /* Return a textedit char token given a textedit text token */
  562.  
  563. pascal OSErr CharFromTEText(DescType desiredClass,
  564.                             const AEDesc *container,
  565.                             DescType containerClass,
  566.                             DescType form,
  567.                             const AEDesc *selectionData,
  568.                             AEDesc *theToken,
  569.                             long LongInt)
  570. {
  571.     AEDesc            startSpec;
  572.     AEDesc            endSpec;
  573.     AEDesc            rangeRecord;
  574.     AEDesc            startToken;
  575.     AEDesc            endToken;
  576.     long            count;
  577.     AEDesc            longKeyData;
  578.     TEHandle        textH;
  579.     Handle            rawTextHdl;
  580.     long            numRawChars;
  581.     TETextTokenBody    tokData;
  582.     short            offset;
  583.     OSErr            err;
  584.  
  585.     /* Check that the container is what we intend.  This should only happen if we
  586.      * installed the token handler incorrectly.
  587.      */
  588.  
  589.     if ( container->descriptorType != typeTEText )
  590.         return errAEEventNotHandled;
  591.     
  592.     /* find the text based on the key form */
  593.     
  594.     switch ( form ){
  595.         case formAbsolutePosition:
  596.             /* Make sure we really have a type long descriptor */
  597.             err = AECoerceDesc( selectionData, typeLongInteger, &longKeyData );
  598.             if ( err ){
  599.                 Gripe( "\pAECoerceDesc failed" );
  600.                 return err;
  601.             }
  602.             
  603.             count = **(long**)(longKeyData.dataHandle);
  604.             
  605.             /* We're done with the descriptor created in the coercion */
  606.             
  607.             err = AEDisposeDesc( &longKeyData );
  608.             if ( err ){
  609.                 Gripe( "\pAEDisposeDesc failed" );
  610.                 return err;
  611.             }
  612.  
  613.             textH = (*(TETextTokenBody**)(container->dataHandle))->textH;
  614.  
  615.             rawTextHdl = (Handle)TEGetText( textH );
  616.             
  617.             numRawChars = GetHandleSize( rawTextHdl );
  618.             
  619.             if ( count > 0 ){
  620.                 /* The position is relative to the beginning */
  621.                 if ( count > numRawChars ){
  622.                     Gripe( "\pAsked for character past end" );
  623.                     return errAENoSuchObject;        /* Wanted char that was past end */
  624.                 }
  625.                 
  626.                 offset = (short) count;
  627.     
  628.             } else {
  629.                 /* In this case, the position is relative to the end of the text */
  630.  
  631.                 if ( count < -numRawChars ){
  632.                     Gripe( "\pAsked for character before beginning" );
  633.                     return errAENoSuchObject;
  634.                 }
  635.                 
  636.                 offset = (short)(numRawChars + count);        /* Note that count is negative */
  637.             }
  638.             /* Actually create the token that we return. */
  639.             
  640.             tokData.textH = textH;
  641.             tokData.startPos = offset;
  642.             tokData.length = 1;                        /* formAbs can have only 1 char */
  643.             tokData.propertyCode = typeNull;        /* This means it's not a property */
  644.  
  645.             err = AECreateDesc( typeTEText, (Ptr)&tokData, sizeof( tokData ), theToken );
  646.             if ( err ){
  647.                 Gripe( "\pAECreateDesc failed to create a token" );
  648.                 return err;
  649.             }
  650.  
  651.             return noErr;
  652.             break;
  653.         case formRelativePosition:
  654.             return errAEEventNotHandled;
  655.             break;
  656.         case formTest:
  657.             return errAEEventNotHandled;
  658.             break;
  659.         case formRange:
  660.  
  661.             /* We must coerce the spec to an AERecrd so GetKeyDesc will know what
  662.              * to do with it.
  663.              */
  664.             
  665.             err = AECoerceDesc( selectionData,
  666.                             typeAERecord,
  667.                             &rangeRecord );
  668.             if ( err )
  669.                 return err;
  670.  
  671.             err = AEGetKeyDesc( &rangeRecord,
  672.                                 keyAERangeStart,
  673.                                 typeObjectSpecifier,
  674.                                 &startSpec );
  675.             if ( err ){
  676.                 Gripe( "\pAEGetKeyDesc failed" );
  677.                 return err;
  678.             }
  679.             
  680.             err = AEGetKeyDesc( &rangeRecord,
  681.                                 keyAERangeStop,
  682.                                 typeObjectSpecifier,
  683.                                 &endSpec );
  684.             if ( err ){
  685.                 Gripe( "\pAEGetKeyDesc failed" );
  686.                 return err;
  687.             }
  688.             
  689.             /* Now we have the object specifiers for the beginning and end of the
  690.              * range.  We call AEResolve to give us tokens for the items that are
  691.              * at each end.  Note that this causes two recursive calls to this very
  692.              * function.
  693.              */
  694.  
  695.             err = AEResolve( &startSpec, kAEIDoMinimum, &startToken );
  696.             if ( err )
  697.                 return err;
  698.             
  699.             /* 1.1.1 MDC fix a memory leak */
  700.             
  701.             err = AEDisposeDesc( &startSpec );
  702.             if ( err )
  703.                 return err;
  704.             
  705.             err = AEResolve( &endSpec, kAEIDoMinimum, &endToken );
  706.             if ( err )
  707.                 return err;
  708.             
  709.             err = AEDisposeDesc( &endSpec );
  710.             if ( err )
  711.                 return err;
  712.             
  713.             
  714.  
  715.             /* Create the return token */
  716.             tokData.textH = (*(TETextTokenBody**)(container->dataHandle))->textH;
  717.             
  718.             tokData.startPos = (*(TETextTokenBody**)(startToken.dataHandle))->startPos;
  719.             
  720.             /* MDC 1.0d10 I have an off-by-one error in the speller's formAbsolutePosition
  721.              * relative to the end, that makes 0 be the last character.  It should
  722.              * be -1.  I should have added 1 here, which masked the bug.
  723.              */
  724.              
  725.             tokData.length = 1 + (*(TETextTokenBody**)(endToken.dataHandle))->startPos -
  726.                                 (*(TETextTokenBody**)(startToken.dataHandle))->startPos;
  727.  
  728.             tokData.propertyCode = typeNull;        /* This means it's not a property */
  729.  
  730.             err = AECreateDesc( typeTEText, (Ptr)&tokData, sizeof( tokData ), theToken );
  731.             if ( err ){
  732.                 Gripe( "\pAECreateDesc failed to create a token" );
  733.                 return err;
  734.             }
  735.  
  736.             return noErr;
  737.             break;
  738.         case formPropertyID:
  739.             return errAEEventNotHandled;
  740.             break;
  741.         case formName:
  742.             return errAEEventNotHandled;    /* Flesh this out later */
  743.             break;
  744.         default:
  745.             Gripe( "\pGot unexpected key form" );
  746.             return errAEEventNotHandled;
  747.     }
  748.         
  749.     return noErr;
  750. }
  751.  
  752. /* Return a property token given a textedit text token.
  753.  * This works for any property of a textedit text item
  754.  */
  755.  
  756. pascal OSErr PropFromTEText(DescType desiredClass,
  757.                             const AEDesc *container,
  758.                             DescType containerClass,
  759.                             DescType form,
  760.                             const AEDesc *selectionData,
  761.                             AEDesc *theToken,
  762.                             long LongInt)
  763. {
  764.     OSErr                err;
  765.     DescType            propType;
  766.  
  767.     /* Check that the container is what we intend.  This should only happen if we
  768.      * installed the token handler incorrectly.
  769.      */
  770.  
  771.     if ( container->descriptorType != typeTEText )
  772.         return errAEEventNotHandled;
  773.  
  774.     if ( form != formPropertyID ){
  775.         Gripe( "\pExpected formPropertyID" );
  776.         return errAEEventNotHandled;
  777.     }
  778.  
  779.     propType = **( (DescType**)(selectionData->dataHandle) );
  780.  
  781.     /* All we really do here is shove the property type into the token, if we
  782.      * know about the property type
  783.      */
  784.  
  785.     switch ( propType ){
  786.         case pBackgroundHilite:
  787.             /* This property contains a selection which is displayed while in the
  788.              * background.  All we do here is set the property code in the token
  789.              * (This is done after the switch).
  790.              */
  791.             break;
  792.         case pClass:
  793.         case pColor:
  794.         case pFont:
  795.         case pPointSize:
  796.         case pScriptTag:
  797.         case pTextStyles:
  798.             Gripe( "\pGot a property type we do not yet implement" );
  799.             return errAENoSuchObject;
  800.             break;
  801.         default:
  802.             Gripe( "\pUnknown property type" );
  803.             return errAENoSuchObject;
  804.             break;
  805.     }
  806.  
  807.     /* All we do in the token is put the propType into it.  We can start with the
  808.      * container descriptor as it has some of the fields filled in already.
  809.      */
  810.     
  811.     err = AEDuplicateDesc( container, theToken );
  812.     if ( err ){
  813.         Gripe( "\pAEDuplicateDesc failed" );
  814.         return err;
  815.     }
  816.     
  817.     (*(TETextTokenBody**)(theToken->dataHandle))->propertyCode = propType;
  818.     
  819.     return noErr;
  820. }
  821.  
  822. /* 
  823.  * Utilities for creating object specifiers
  824.  */
  825.  
  826. OSErr CreateWindTextSpec( WindowPtr wp, long textNumber, AEDesc *specPtr )
  827. {
  828.     WindowPtr    candidate;
  829.     long        windowNumber;
  830.     OSErr        err;
  831.  
  832.     /* Search through the open windows (yes I know there's only one) looking
  833.      * for the one matching the given pointer.  This routine will be useful in
  834.      * multiple-window programs.
  835.      */
  836.     
  837.     if ( !wp )
  838.         return errAENoSuchObject;
  839.     
  840.     candidate = FrontWindow();
  841.     
  842.     if ( !candidate )
  843.         return errAENoSuchObject;
  844.         
  845.     windowNumber = 1;
  846.     
  847.     while ( candidate != wp ){
  848.         candidate = (WindowPtr)((WindowPeek)candidate)->nextWindow;
  849.         if ( !candidate )
  850.             return errAENoSuchObject;
  851.         
  852.         windowNumber++;
  853.     }
  854.  
  855.     err = CreateTextSpecifier( windowNumber, textNumber, specPtr );
  856.  
  857.     return err;
  858. }
  859.  
  860. OSErr CreateTextSpecifier( long windowNumber, long textNumber, AEDesc *specPtr )
  861. {
  862.     AEDesc    docSpecifier;
  863.     AEDesc    textDescriptor;
  864.     OSErr    err;
  865.  
  866.     /* Create an object specifier for a particular text field within a particular
  867.      * window. In each case use formAbsolutePosition.
  868.      */
  869.  
  870.     err = BuildWindowSpecifier( &docSpecifier, windowNumber );
  871.     if ( err ){
  872.         Gripe( "\pBuildWindowDescriptor failed" );
  873.         return err;
  874.     }
  875.     
  876.     err = CreateOffsetDescriptor( textNumber, &textDescriptor );
  877.     if ( err ){
  878.         Gripe( "\pCreateOffsetDescriptor failed" );
  879.         return err;
  880.     }
  881.     
  882.     err = CreateObjSpecifier( cText,
  883.                                 &docSpecifier,
  884.                                 formAbsolutePosition,
  885.                                 &textDescriptor,
  886.                                 true,                        /* Dispose input descriptors */
  887.                                 specPtr );
  888.     if ( err ){
  889.         Gripe( "\pCreateObjSpecifer failed" );
  890.         return err;
  891.     }
  892.  
  893.     return noErr;
  894. }
  895.  
  896. /*
  897.  * Object counting routines.
  898.  */
  899.  
  900. OSErr CountTextInWind( WindowPtr wp, long *countPtr )
  901. {
  902.     /* For us, there can only be one text field in a window.  If you have
  903.      * a structured document such as a spreadsheet, you should count the total
  904.      * number of text cells.
  905.      */
  906.     
  907.     *countPtr = 1L;
  908.  
  909.     return noErr;
  910. }
  911.  
  912.  
  913. /*
  914.  * Coercion handlers
  915.  */
  916.  
  917. /* Convert a pointer to raw text to a Pascal string.  This is such a simple one it
  918.  * ought to have been built into the system!
  919.  */
  920.  
  921. pascal OSErr TextPtrToPString( DescType typeCode,
  922.                                 Ptr dataPtr,
  923.                                 Size dataSize,
  924.                                 DescType toType,
  925.                                 long handlerRefCon,
  926.                                 AEDesc *resultPtr )
  927. {
  928.     char    *buf;
  929.     OSErr    err;
  930.     Ptr        myDataPtr;
  931.     Size    myDataSize;
  932.  
  933.     if ( toType != typePString )
  934.         return errAECoercionFail;
  935.  
  936.     /* This one routine could reasonably coerce from several different sorts of textual
  937.      * data.  We switch off the types and set a pointer to where the text data actually
  938.      * starts.  Don't have any "from" types but typeChar so far.
  939.      */
  940.  
  941.     switch ( typeCode ){
  942.         case typeChar:
  943.             myDataPtr = dataPtr;
  944.             myDataSize = dataSize;
  945.             break;
  946.         default:
  947.             Gripe( "\pCannot coerce requested type" );
  948.             return errAECoercionFail;
  949.             break;
  950.     }
  951.  
  952.     if ( myDataSize > 255 || myDataSize < 0 )
  953.         return errAECoercionFail;
  954.  
  955.     buf = NewPtr( myDataSize + 1 );
  956.     if ( !buf )
  957.         return memFullErr;
  958.     
  959.     buf[0] = myDataSize;
  960.     BlockMove( myDataPtr, &(buf[1]), dataSize );
  961.     
  962.     err = AECreateDesc( typePString,
  963.                         buf,
  964.                         myDataSize + 1,
  965.                         resultPtr );
  966.                         
  967.     return err;
  968. }
  969.